<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="RYUIX" id="RYUIX"><span data-lake-id="u343870cd" id="u343870cd">典型回答</span></h1>
  <p data-lake-id="u6c18419d" id="u6c18419d"><span data-lake-id="u6c9d8346" id="u6c9d8346">当JVM判断对象不再存活的时候，便会在下一次GC时候将该对象回收掉，为堆腾出空间，那么JVM如何判断对象是否存活呢？</span></p>
  <p data-lake-id="ua762bda1" id="ua762bda1"><span data-lake-id="uf42ee584" id="uf42ee584">​</span><br></p>
  <p data-lake-id="u32dd859e" id="u32dd859e"><span data-lake-id="ue5a2ebdb" id="ue5a2ebdb">JVM有两种算法来判断对象是否存活，分别是</span><strong><span data-lake-id="u681c0f14" id="u681c0f14">引用计数法</span></strong><span data-lake-id="uafd818a3" id="uafd818a3">和</span><strong><span data-lake-id="u962a1038" id="u962a1038">可达性分析算法</span></strong></p>
  <p data-lake-id="u7f5ce520" id="u7f5ce520"><strong><span data-lake-id="u21d7b4df" id="u21d7b4df">​</span></strong><br></p>
  <ul list="u0c1e73ff">
   <li fid="uf28a2835" data-lake-id="c2c15c6a2d3bf3c8fe07f5d5bd7f3963_li_47" id="c2c15c6a2d3bf3c8fe07f5d5bd7f3963_li_47"><strong><span data-lake-id="u31cffbd4" id="u31cffbd4">引用计数法</span></strong><span data-lake-id="u17033f0b" id="u17033f0b">：给对象中添加一个引用计数器，每当有一个地方引用它，计数器就加 1；当引用失效，计数器就减 1；任何时候计数器为 0 的对象就是不可能再被使用的。</span><strong><span data-lake-id="u990a98e0" id="u990a98e0">这个方法实现简单，效率高，但是目前主流的虚拟机中并没有选择这个算法来管理内存，其最主要的原因是它很难解决对象之间相互循环引用的问题。</span></strong></li>
  </ul>
  <p data-lake-id="u74df9cd2" id="u74df9cd2"><strong><span data-lake-id="ud126113e" id="ud126113e">​</span></strong><br></p>
  <blockquote data-lake-id="u1a204a8d" id="u1a204a8d">
   <p data-lake-id="ub89b3b4e" id="ub89b3b4e"><span data-lake-id="u0ce4f65a" id="u0ce4f65a">循环引用会导致对象无法被回收，最终会导致内存泄漏及内存溢出</span></p>
  </blockquote>
  <p data-lake-id="u631b4a8c" id="u631b4a8c"><br></p>
  <ul list="u0c1e73ff" start="2">
   <li fid="uf28a2835" data-lake-id="8158a717a2f09165760f69f4b35afa38_li_48" id="8158a717a2f09165760f69f4b35afa38_li_48"><strong><span data-lake-id="u339681fa" id="u339681fa">可达性分析算法</span></strong><span data-lake-id="u7b91fd9f" id="u7b91fd9f">： 这个算法的基本思想就是通过一系列的称为 </span><strong><span data-lake-id="u111324ff" id="u111324ff">“GC Roots”</span></strong><span data-lake-id="ub73cb7d7" id="ub73cb7d7"> 的对象作为起点，从这些节点开始向下搜索，节点所走过的路径称为引用链，当一个对象到 GC Roots 没有任何引用链相连的话，则证明此对象是不可用的。</span></li>
  </ul>
  <p data-lake-id="u0de54033" id="u0de54033"><br></p>
  <p data-lake-id="u6dfbfa09" id="u6dfbfa09"><span data-lake-id="u35b8e035" id="u35b8e035">但是，并不是说当进行完可达性分析算法后，即可证明某对象可以被GC。对象是否存活，需要两次标记：</span></p>
  <ol list="u798d0087">
   <li fid="ubbb537d3" data-lake-id="ud80788a7" id="ud80788a7"><span data-lake-id="u2ce0afc1" id="u2ce0afc1">第一次标记通过可达性分析算法。如果没有GC Roots相连接的引用链，那么将第一次标记</span></li>
   <li fid="ubbb537d3" data-lake-id="u91a87f5f" id="u91a87f5f"><span data-lake-id="u435fdda9" id="u435fdda9">如果对象的</span><code data-lake-id="u7f3927b3" id="u7f3927b3"><span data-lake-id="u2e606764" id="u2e606764">finalize()</span></code><span data-lake-id="u23c2a789" id="u23c2a789">方法被覆盖并且没有执行过，则放在F-Queue队列中等待执行</span><em><span data-lake-id="uf78c3d90" id="uf78c3d90">(不一定会执行)</span></em><span data-lake-id="u08d7d087" id="u08d7d087">，如果一段时间后该队列的</span><code data-lake-id="udc8fd228" id="udc8fd228"><span data-lake-id="u8f4ddd90" id="u8f4ddd90">finalize()</span></code><span data-lake-id="u10f8fc04" id="u10f8fc04">方法被执行且和GC Roots关联，则移出“即将回收”集合。如果仍然没有关联，则进行第二次标记，才会对该对象进行回收</span></li>
  </ol>
  <p data-lake-id="ud7c52043" id="ud7c52043"><br></p>
  <p data-lake-id="uee60f86a" id="uee60f86a"><span data-lake-id="udbe5d6cd" id="udbe5d6cd">不过现在都不提倡覆盖</span><code data-lake-id="ueaa2ac6a" id="ueaa2ac6a"><span data-lake-id="u4abcbd54" id="u4abcbd54">finalize</span></code><span data-lake-id="u65468fdf" id="u65468fdf">方法，它的本意是像Cpp一样在对象销毁前执行，但是它影响了JAVA的安全和GC的性能，所以第二种判断会越来越少</span></p>
  <h1 data-lake-id="OQ8Fb" id="OQ8Fb"><span data-lake-id="ud7701323" id="ud7701323">知识扩展</span></h1>
  <p data-lake-id="u4733f67f" id="u4733f67f"><br></p>
  <h2 data-lake-id="tnBTG" id="tnBTG"><span data-lake-id="u1bb0dead" id="u1bb0dead">哪些对象可以作为GC roots？</span></h2>
  <p data-lake-id="ud634f8c4" id="ud634f8c4"><br></p>
  <p data-lake-id="ufa12dd19" id="ufa12dd19"><span data-lake-id="u534c3f96" id="u534c3f96" style="color: rgb(18, 18, 18)">GC roots是作为可达性分析算法的</span><strong><span data-lake-id="ubc1ba618" id="ubc1ba618" style="color: rgb(18, 18, 18)">起点的</span></strong><span data-lake-id="u99585765" id="u99585765" style="color: rgb(18, 18, 18)">。要实现语义正确的可达性分析，就必须要能完整枚举出</span><strong><span data-lake-id="u8c843229" id="u8c843229" style="color: rgb(18, 18, 18)">所有的GC Roots</span></strong><span data-lake-id="u7d2fdde2" id="u7d2fdde2" style="color: rgb(18, 18, 18)">，否则就可能会漏扫描应该存活的对象，导致GC错误回收了这些被漏扫的活对象。那么，所谓“GC Roots”，就是</span><strong><span data-lake-id="uc932ffc6" id="uc932ffc6" style="color: rgb(18, 18, 18)">一组必须活跃的引用</span></strong><span data-lake-id="uc058ef35" id="uc058ef35" style="color: rgb(18, 18, 18)">。</span></p>
  <p data-lake-id="ud0bfdc3b" id="ud0bfdc3b"><span data-lake-id="u1c98bd6f" id="u1c98bd6f">​</span><br></p>
  <p data-lake-id="u6757b133" id="u6757b133"><span data-lake-id="u9cf32575" id="u9cf32575">那么，有哪些引用是一定活跃的呢？看下下面这些是不是都符合这个条件：</span></p>
  <p data-lake-id="ucef9856a" id="ucef9856a"><span data-lake-id="u936ca911" id="u936ca911">​</span><br></p>
  <ul list="ud412ed41">
   <li fid="u465f606f" data-lake-id="u27da7398" id="u27da7398"><span data-lake-id="ub08a2d99" id="ub08a2d99">Class - 由系统类加载器(system class loader)加载的对象，这些类是不能够被回收的，他们可以以静态字段的方式保存持有其它对象。</span></li>
   <li fid="u465f606f" data-lake-id="ubeafdb3c" id="ubeafdb3c"><span data-lake-id="u60662c72" id="u60662c72">Thread - 活着的线程</span></li>
   <li fid="u465f606f" data-lake-id="u8441cef1" id="u8441cef1"><span data-lake-id="u86d48ccc" id="u86d48ccc">Stack Local - Java方法的local变量或参数</span></li>
   <li fid="u465f606f" data-lake-id="u5c18af48" id="u5c18af48"><span data-lake-id="u1c9e94af" id="u1c9e94af">JNI Local - JNI方法的local变量或参数</span></li>
   <li fid="u465f606f" data-lake-id="u86fd7fb0" id="u86fd7fb0"><span data-lake-id="u7007caba" id="u7007caba">JNI Global - 全局JNI引用</span></li>
   <li fid="u465f606f" data-lake-id="u28290a1c" id="u28290a1c"><span data-lake-id="u631bc75e" id="u631bc75e">Monitor Used - 被同步锁（synchronized）持有的对象</span></li>
   <li fid="u465f606f" data-lake-id="u359a4735" id="u359a4735"><span data-lake-id="u53113989" id="u53113989">Held by JVM - 用于JVM特殊目的由GC保留的对象，但实际上这个与JVM的实现是有关的。可能已知的一些类型是：系统类加载器、一些JVM知道的重要的异常类、一些用于处理异常的预分配对象以及一些自定义的类加载器等。然而，JVM并没有为这些对象提供其它的信息，因此需要去确定哪些是属于"JVM持有"的了。</span></li>
  </ul>
  <p data-lake-id="u0c42bc88" id="u0c42bc88"><br></p>
  <p data-lake-id="ud20b9ed8" id="ud20b9ed8"><br></p>
  <p data-lake-id="udd218bad" id="udd218bad"><span data-lake-id="ua99428b0" id="ua99428b0">以上，比如系统类加载器加载的对象、活着的线程、方法中的本地变量、被synchronized锁定的对象这些，都是</span><strong><span data-lake-id="u5c55fea8" id="u5c55fea8">符合活跃</span></strong><span data-lake-id="ud9332939" id="ud9332939">的引用这个条件的！</span></p>
  <p data-lake-id="ucc0530eb" id="ucc0530eb"><br></p>
  <p data-lake-id="u27ad4d30" id="u27ad4d30"><span data-lake-id="u8d7e1d0d" id="u8d7e1d0d">除了这些， 还有一种，基本上很少有人提到的，大家去看网上的所有关于介绍GC Root的八股文，基本上没人提的，那就是为了解决跨代引用的问题，会把Remembered Set也作为GC Root。</span></p>
  <p data-lake-id="udb2a40c2" id="udb2a40c2"><span data-lake-id="u22e43aca" id="u22e43aca">​</span><br></p>
  <p data-lake-id="u90ac9a56" id="u90ac9a56"><br></p>
  <h2 data-lake-id="YJ67M" id="YJ67M"><strong><span data-lake-id="u8504df84" id="u8504df84">可达性分析算法的不足</span></strong></h2>
  <p data-lake-id="u35e4a723" id="u35e4a723"><br></p>
  <p data-lake-id="ue8dc3897" id="ue8dc3897"><span data-lake-id="u85c31a5c" id="u85c31a5c">虽然可达性分析算法相比于引用计数法要好很多，但是他也不是毫无缺点的。这种算法主要存在以下几个不足：</span></p>
  <p data-lake-id="u5eb6fd30" id="u5eb6fd30"><br></p>
  <h3 data-lake-id="kbmMB" id="kbmMB"><span data-lake-id="u27d25d4e" id="u27d25d4e">STW时间长</span></h3>
  <p data-lake-id="u9e8f4827" id="u9e8f4827"><br></p>
  <p data-lake-id="u3b742450" id="u3b742450"><span data-lake-id="u265d3271" id="u265d3271">可达性分析算法需要对程序进行全局分析，因此时间复杂度较高，可能需要很长的时间才能完成分析，并且整个过程都是STW的，所以对应用的整体性能有很大影响。这也使得可达性分析算法难以适用于大型程序的分析。所以一些常见的回收器都会使用一些优化技术来减少可达性分析的时间和开销，如增量标记、增量拷贝等。</span></p>
  <p data-lake-id="ue4a9f544" id="ue4a9f544"><span data-lake-id="ue147aa0a" id="ue147aa0a">​</span><br></p>
  <p data-lake-id="u9d86ecdc" id="u9d86ecdc"><span data-lake-id="u7d4047a7" id="u7d4047a7">解决这个问题，主要是依赖三色标记法</span></p>
  <p data-lake-id="u330aaadb" id="u330aaadb"><span data-lake-id="u9e6b54cd" id="u9e6b54cd">​</span><br></p>
  <p data-lake-id="u29cf6105" id="u29cf6105"><br></p>
  <h3 data-lake-id="h0mV1" id="h0mV1"><span data-lake-id="ubf1b2c01" id="ubf1b2c01" style="color: rgb(55, 65, 81); background-color: rgb(247, 247, 248)">内</span><span data-lake-id="u34ea88f2" id="u34ea88f2">存消耗</span></h3>
  <p data-lake-id="u444b8ae7" id="u444b8ae7"><span data-lake-id="u984c3e52" id="u984c3e52">​</span><br></p>
  <p data-lake-id="ud5eb6cf6" id="ud5eb6cf6"><span data-lake-id="ufa3c911a" id="ufa3c911a">可达性分析算法需要存储程序中所有的对象和它们之间的引用关系，这些信息需要占用大量的内存空间。对于大型程序，如果要进行完整的可达性分析，需要存储的对象数量和引用关系数量都非常大，可能会导致内存空间不足或者程序性能下降的问题。</span></p>
 </body>
</html>